package com.example.xmlfind.worker;

import com.example.xmlfind.entity.FindResult;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.source.xml.XmlFileImpl;
import com.intellij.psi.tree.IFileElementType;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;

import javax.swing.*;
import java.awt.*;
import java.util.List;
import java.util.*;

/**
 * 查找出现两次或两次以上的标签
 * <p>
 * 现在只支持单个属性匹配
 */
public class FindWorker extends SwingWorker<Void, Integer> {
    String tagNameInput;
    String keyNameInput;
    String keyValueInput;
    Project project;
    JButton findButton;
    JButton cancelButton;
    JProgressBar findProgress;
    DefaultListModel<VirtualFile> dirModel;
    DefaultListModel<FindResult> findModel;
    private final HashMap<String, HashMap<String, LinkedList<FindResult>>> data = new HashMap<>();

    public FindWorker(String tagNameInput, String keyNameInput, String keyValueInput, Project project, JButton findButton, JButton cancelButton,
                      JProgressBar findProgress, DefaultListModel<VirtualFile> dirModel, DefaultListModel<FindResult> findModel) {
        this.tagNameInput = tagNameInput;
        this.keyNameInput = keyNameInput;
        this.keyValueInput = keyValueInput;
        this.project = project;
        this.findButton = findButton;
        this.cancelButton = cancelButton;
        this.findProgress = findProgress;
        this.dirModel = dirModel;
        this.findModel = findModel;
    }

    int xmlFileCount = 0;
    int currentFileCount = 0;

    @Override
    protected Void doInBackground() throws Exception {
        // 计算文件总数
        for (int i = 0, len = dirModel.getSize(); i < len; i++) {
            xmlFileCount += getXmlFileCount(dirModel.getElementAt(i));
        }
        findProgress.setMaximum(xmlFileCount);

        for (int i = 0, len = dirModel.getSize(); i < len; i++) {
            VirtualFile f = dirModel.getElementAt(i);
            processDirectory(PsiManager.getInstance(project), f);
        }

        // 挑选重复的添加到列表中
        for (Map.Entry<String, HashMap<String, LinkedList<FindResult>>> tagEntry : data.entrySet()) {
            for (Map.Entry<String, LinkedList<FindResult>> keyEntry : tagEntry.getValue().entrySet()) {
                LinkedList<FindResult> values = keyEntry.getValue();
                if (values.size() < 2) break;

                // 排序一下，使列表有序
                Collections.sort(values);

                // 出现超过两次的值才添加进列表
                HashMap<FindResult, Integer> m = new HashMap<>();
                values.forEach(findResult -> m.merge(findResult, 1, Integer::sum));
                // 到事件线程执行更新
                EventQueue.invokeLater(() -> {
                    values.stream()
                            .filter(findResult -> m.get(findResult) != 1)
                            .forEach(findResult -> {
                                findModel.addElement(findResult);
                            });
                    // findModel.addAll(values);
                    // 充当分割符
                    findModel.addElement(null);
                });
            }
        }
        return null;
    }

    @Override
    protected void process(List<Integer> chunks) {
        findProgress.setValue(chunks.get(chunks.size() - 1));
    }

    @Override
    protected void done() {
        data.clear();
        findButton.setEnabled(true);
        cancelButton.setText("清空");
    }

    private int getXmlFileCount(VirtualFile f) {
        if (!f.isDirectory() && f.getFileType() instanceof XmlFileType) return 1;
        return Arrays.stream(f.getChildren())
                .reduce(0, (integer, virtualFile) -> integer + getXmlFileCount(virtualFile), Integer::sum);
    }

    /**
     * 递归处理目录
     */
    private void processDirectory(PsiManager psiManager, VirtualFile f) {
        if (Thread.currentThread().isInterrupted()) {
            done();
            return;
        }
        if (!f.isDirectory()) {
            // 只要XML文件
            if (f.getFileType() instanceof XmlFileType) {
                // 摸索了好久才找到这个构造器的
                XmlFileImpl xmlFile = new XmlFileImpl(psiManager.findViewProvider(f), new IFileElementType(XMLLanguage.INSTANCE));
                processFile(xmlFile);
            }
        } else {
            Arrays.stream(f.getChildren()).forEach(child -> {
                if (Thread.currentThread().isInterrupted()) return;
                processDirectory(psiManager, child);
            });
        }
    }

    XmlDocument document;
    Application application = ApplicationManager.getApplication();
    XmlTag rootTag;

    /**
     * 处理XML文件
     */
    public void processFile(XmlFile f) {
        rootTag = null;
        // 这个方法是线程安全的，会阻塞
        application.runReadAction(() -> {
            document = f.getDocument();
            if (document == null) return;
            rootTag = document.getRootTag();
        });
        if (rootTag != null) recursiveTag(rootTag);
        currentFileCount++;
        publish(currentFileCount);
    }

    XmlAttribute[] attributes;

    String keyName;
    String keyValue;
    String text;
    PsiFile containingFile;
    XmlTag[] subTags;

    /**
     * 递归处理XML文件标签
     */
    public void recursiveTag(XmlTag rootTag) {
        keyName = null;
        keyValue = null;

        String tagName = rootTag.getName();
        boolean tagNameEmpty = "".equals(tagNameInput);
        boolean keyNameEmpty = "".equals(keyNameInput);
        boolean keyValueEmpty = "".equals(keyValueInput);

        HashMap<String, LinkedList<FindResult>> map = data.computeIfAbsent(tagName, k -> new HashMap<>());
        application.runReadAction(() -> {
            containingFile = rootTag.getContainingFile();
        });

        if (tagNameEmpty || tagName.toLowerCase().contains(tagNameInput)) {
            LinkedList<FindResult> values = map.computeIfAbsent(keyName, k -> new LinkedList<>());
            if (keyNameEmpty && keyValueEmpty) {
                application.runReadAction(() -> {
                    text = rootTag.getText();
                });

                // 不考虑属性
                int i = text.indexOf("\n");
                String lineText = i == -1 ? text : text.substring(0, i);

                values.push(new FindResult(
                        containingFile, // 文件
                        keyValue, // 属性值null
                        lineText, // 所在行文本
                        // 也许这个操作在双击列表项定位到文件时再调用会更节省性能
                        getStartOffset(rootTag) // 递归拿到偏移量
                ));
            } else {
                // 考虑所有属性
                application.runReadAction(() -> {
                    attributes = rootTag.getAttributes();
                });

                for (XmlAttribute attribute : attributes) {
                    application.runReadAction(() -> {
                        keyName = attribute.getName();
                        keyValue = attribute.getValue();
                        text = attribute.getText();
                    });

                    int i = text.indexOf("\n");
                    String lineText = i == -1 ? text : text.substring(0, i);

                    // 不在填写属性名和属性值范围的属性不处理
                    if ((keyName.toLowerCase().contains(keyNameInput) && keyValue.toLowerCase().contains(keyValueInput))) {
                        values.push(new FindResult(
                                containingFile, // 文件
                                keyValue, // 属性值
                                lineText, // 所在行文本
                                getStartOffset(attribute)// 偏移量
                        ));
                    }
                }
            }
        }

        // 递归子标签
        application.runReadAction(() -> {
            subTags = rootTag.getSubTags();
        });
        for (XmlTag subTag : subTags) {
            recursiveTag(subTag);
        }
    }

    // 标签的位置需要递归才能获取到
    public static int getStartOffset(XmlTag xmlTag) {
        if (xmlTag == null) return 0;
        return xmlTag.getStartOffsetInParent() + getStartOffset(xmlTag.getParentTag());
    }

    public static int getStartOffset(XmlAttribute attribute) {
        if (attribute == null) return 0;
        return attribute.getStartOffsetInParent() + getStartOffset(attribute.getParent());
    }

}
